package survivalstagegenerator;

import survivalstagegenerator.Opponents.Mark;
import survivalstagegenerator.Opponents.Jack;
import survivalstagegenerator.Opponents.Sorcerer;
import survivalstagegenerator.Opponents.Monk;
import survivalstagegenerator.Opponents.Jan;
import survivalstagegenerator.Opponents.Knight;
import survivalstagegenerator.Opponents.Justin;
import survivalstagegenerator.Opponents.Template;
import survivalstagegenerator.Opponents.RandomID3000;
import survivalstagegenerator.Opponents.RandomID1000;
import java.util.Random;
import java.util.ArrayList;

public class Phase {
    protected ArrayList<Opponent> opponents;
    protected int maxPhaseStrength;
    protected int bound;
    protected String phaseComment;
    protected String phaseSourceCode;
    private final int[] ADD_OPPONENT_PROBABILITY;
    protected final int[] TIMES_PROBABILITY;
    // The calculation of strength of an opponent in a phase includes HP. 5 HP are worth 1 strength.
    private static final int STRENGTH_PER_5HP = 1;
    protected static final double STRENGTH_FACTOR_IN_CASE_OF_TIMES = 1.5;  // currently not used
    
    public Phase(final int phaseStrength, final int bound, final String phaseComment) {
        this.phaseSourceCode = "";
        // The probability for adding opponents to a phase is defined in the following array. Initially the probability is 100%.
        // If adding more opponents is more likely to happen, then increasing HP is comparatively less often happening. 
        this.ADD_OPPONENT_PROBABILITY = new int[] {100, 80, 70, 55, 45, 35, 35, 25, 25, 15, 15, 10, 10, 5, 5, 3, 3, 2, 2};
        // After n opponents are added to a phase the probability for adding times to any of them is defined in the following array.
        this.TIMES_PROBABILITY = new int[] {0, 0, 20, 35, 50, 50, 50, 50, 50};
        this.opponents = new ArrayList<Opponent>();
        this.maxPhaseStrength = phaseStrength;
        this.bound = bound;
        this.phaseComment = phaseComment;
    }
    
    public void generatePhase() {
        final Random gen = new Random();
        int currentPhaseStrength = 0;
        
        // Fill up the max phase strength with opponents.
        while (currentPhaseStrength < this.maxPhaseStrength) {
        	
        	// TODO: This could be optimized. Just add to an opponent counter, 
        	// whenever an opponent is added.
            final int currentOpponentCount = this.opponents.size();
            
            // TODO: This could be optimized. Just add difficulty to a variable 
            // whenever anything is added to the phase (opponents, times, HP, 
            // ...) instead of recalculating at every iteration.
            currentPhaseStrength = this.calculateCurrentPhaseStrength();
            
            int addOpponentRandNum = gen.nextInt(100);
            
            // Initially always add at least one opponent.
            if (currentOpponentCount == 0) {
            	this.addOpponent();
                continue;
            }
            
            // If there are still probability values available in the 
            // ADD_OPPONENT_PROBABILITY array, then roll for adding 
            // opponents to happen.
            if (currentOpponentCount <= this.ADD_OPPONENT_PROBABILITY.length) {
            	if (addOpponentRandNum >= this.ADD_OPPONENT_PROBABILITY[currentOpponentCount % this.ADD_OPPONENT_PROBABILITY.length]) {
            		this.increaseHP();
            	} else {
            		this.addOpponent();
            	}
            } else {
            	// Otherwise use the last available value to roll for 
            	// adding opponents.
            	if (addOpponentRandNum >= this.ADD_OPPONENT_PROBABILITY[this.ADD_OPPONENT_PROBABILITY.length - 1]) {
            		this.increaseHP();
            	} else {
            		this.addOpponent();
            	}
            }
            
            
            // TODO: Improve code readability: Magic numbers 3 and 8. What are they?
            // What follows are some special "magic" rules, which should be better 
            // explained.
            if (currentOpponentCount >= 3 && currentOpponentCount < 8) {
                if (gen.nextInt(100) >= this.TIMES_PROBABILITY[currentOpponentCount]) {
                    continue;
                }
                this.addTimes();
            } 
//            else {
//                if (currentOpponentCount < 3 || currentOpponentCount > 8) {
//                    continue;
//                }
//                // Probably the execution can never get here.
//                if (gen.nextInt(100) >= this.TIMES_PROBABILITY[this.TIMES_PROBABILITY.length - 1]) {
//                    continue;
//                }
//                this.addTimes();
//            }
        }
    }
    
    private void addOpponent() {
        final Random gen = new Random();
        final int randNum = gen.nextInt(10);
        final int randNum2 = gen.nextInt(100);
        switch (randNum) {
            case 0: {
                RandomID1000 oppo0 = new RandomID1000();
                if (randNum2 >= oppo0.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo0);
                    break;
                }
                oppo0 = null;
                break;
            }
            case 1: {
                RandomID3000 oppo2 = new RandomID3000();
                if (randNum2 >= oppo2.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo2);
                    break;
                }
                oppo2 = null;
                break;
            }
            case 2: {
                Template oppo3 = new Template();
                if (randNum2 >= oppo3.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo3);
                    break;
                }
                oppo3 = null;
                break;
            }
            case 3: {
                Justin oppo4 = new Justin();
                if (randNum2 >= oppo4.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo4);
                    break;
                }
                oppo4 = null;
                break;
            }
            case 4: {
                Knight oppo5 = new Knight();
                if (randNum2 >= oppo5.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo5);
                    break;
                }
                oppo5 = null;
                break;
            }
            case 5: {
                Jan oppo6 = new Jan();
                if (randNum2 >= oppo6.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo6);
                    break;
                }
                oppo6 = null;
                break;
            }
            case 6: {
                Monk oppo7 = new Monk();
                if (randNum2 >= oppo7.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo7);
                    break;
                }
                oppo7 = null;
                break;
            }
            case 7: {
                Sorcerer oppo8 = new Sorcerer();
                if (randNum2 >= oppo8.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo8);
                    break;
                }
                oppo8 = null;
                break;
            }
            case 8: {
                Jack oppo9 = new Jack();
                if (randNum2 >= oppo9.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo9);
                    break;
                }
                oppo9 = null;
                break;
            }
            case 9: {
                Mark oppo10 = new Mark();
                if (randNum2 >= oppo10.getNotAddedProbability()) {
                    this.opponents.add((Opponent)oppo10);
                    break;
                }
                oppo10 = null;
                break;
            }
        }
    }
    
    protected void increaseHP() {
        final int currentNumberOfOpponents = this.opponents.size();
        boolean HPIncreased = false;
        boolean HPOfAnyOpponentIncreasable = false;
        for (int i = 0; i < this.opponents.size(); ++i) {
            if (this.opponents.get(i).getHPLevel() < 4) {
                HPOfAnyOpponentIncreasable = true;
            }
        }
        if (HPOfAnyOpponentIncreasable) {
            while (!HPIncreased) {
                final Random gen = new Random();
                final int randnum = gen.nextInt(currentNumberOfOpponents);
                if (this.opponents.get(randnum).getHPLevel() < 4) {
                    this.opponents.get(randnum).setHPLevel(this.opponents.get(randnum).getHPLevel() + 1);
                    HPIncreased = true;
                }
            }
        }
        else {
            final Random gen = new Random();
            final boolean randBool = gen.nextBoolean();
            if (randBool) {
                this.addOpponent();
            }
            else {
                this.addTimes();
            }
        }
    }
    
    protected int calculateCurrentPhaseStrength() {
        int currentPhaseStrength = 0;
        for (int i = 0; i < this.opponents.size(); ++i) {
            final int initStrength = this.opponents.get(i).getInitStrength();
            final int currentHPLevel = this.opponents.get(i).getHPLevel();
            final int initHP = this.opponents.get(i).getHP()[0];
            final int currentHP = this.opponents.get(i).getHP()[currentHPLevel];
            final int opponentTimes = this.opponents.get(i).getTimes();
            final int additionalStrentgh = Phase.STRENGTH_PER_5HP * ((currentHP - initHP) / 5);
            final int totalStrength = (int)Math.floor((initStrength + additionalStrentgh) * Math.pow(1.5, opponentTimes - 1));
            currentPhaseStrength += totalStrength;
        }
        System.out.println("strengh of current phase:" + currentPhaseStrength);
        return currentPhaseStrength;
    }
    
    private void addTimes() {
        final int currentNumberOfOpponents = this.opponents.size();
        boolean timesIncreased = false;
        boolean timesOfAnyOpponentIncreasable = false;
        for (int i = 0; i < this.opponents.size(); ++i) {
            if (this.opponents.get(i).getTimes() < 3) {
                timesOfAnyOpponentIncreasable = true;
            }
        }
        if (timesOfAnyOpponentIncreasable) {
            while (!timesIncreased) {
                final Random gen = new Random();
                final int randNum = gen.nextInt(currentNumberOfOpponents);
                final int randNum2 = gen.nextInt(100);
                if (this.opponents.get(randNum).getTimes() < 4 && randNum2 > this.opponents.get(randNum).getNoTimesAddedProb()) {
                    this.opponents.get(randNum).setTimes(this.opponents.get(randNum).getTimes() + 1);
                    timesIncreased = true;
                }
            }
        }
        else {
            final Random gen = new Random();
            final boolean randBool = gen.nextBoolean();
            if (randBool) {
                this.addOpponent();
            }
            else {
                this.increaseHP();
            }
        }
    }
    
    public String getPhaseSourceCode() {
        this.phaseSourceCode = this.phaseSourceCode + "\t<phase> bound: " + Integer.toString(this.bound) + "  # " + this.phaseComment + "\n";
        this.phaseSourceCode += "\t\tid: 122  x: 100  #milk\n";
        this.phaseSourceCode += "\t\tid: 122  x: 100  #milk  ratio: 1\n";
        for (int i = 0; i < this.opponents.size(); ++i) {
            final int charID = this.opponents.get(i).getCharID();
            final int charHP = this.opponents.get(i).getHP()[this.opponents.get(i).getHPLevel()];
            final int charTimes = this.opponents.get(i).getTimes();
            if (charTimes == 1) {
                this.phaseSourceCode = this.phaseSourceCode + "\t\tid: " + Integer.toString(charID) + "  hp: " + Integer.toString(charHP) + "\n";
            }
            else {
                this.phaseSourceCode = this.phaseSourceCode + "\t\tid: " + Integer.toString(charID) + "  hp: " + Integer.toString(charHP) + "  times: " + Integer.toString(charTimes) + "\n";
            }
        }
        return this.phaseSourceCode += "\t<phase_end>\n\n";
    }
}
